using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.Type;

namespace TelerikMvcGridCustomBindingHelper.NHibernate.Aggregates
{
    [Serializable]
    public sealed class BetterSqlProjection : IProjection
    {
        private readonly bool _ignoreCast;
        private readonly string _sql;
        private readonly string _groupBy;
        private readonly IType[] _types;
        private readonly string[] _aliases;
        private readonly string[] _columnAliases;
        private readonly bool _grouped;

        public BetterSqlProjection(string sql, string columnAlias = "betterSqlProjectionResult", IType returnType = null, bool ignoreCast = false)
            : this(sql, null, new[] { columnAlias }, returnType == null ? new IType[] { NHibernateUtil.Decimal } : new[] { returnType }, ignoreCast) { }

        public BetterSqlProjection(string sql, string groupBy, string[] columnAliases, IType[] types, bool ignoreCast)
        {
            _sql = sql;
            _types = types;
            _ignoreCast = ignoreCast;
            _aliases = columnAliases;
            _columnAliases = columnAliases;
            _grouped = groupBy != null;
            _groupBy = groupBy;
        }

        public SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            return PrivateToSqlString(_sql, criteria, position, criteriaQuery);
        }

        public SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, IDictionary<string, IFilter> enabledFilters)
        {
            return PrivateToSqlString(_groupBy, criteria, 0, criteriaQuery);
        }

        private SqlString PrivateToSqlString(string sql, ICriteria criteria, int position, ICriteriaQuery criteriaQuery)
        {
            var factory = criteriaQuery.Factory;
            var sqlTypeCodes = GetTypes(criteria, criteriaQuery)[0].SqlTypes(factory);
            var sqlType = factory.Dialect.GetCastTypeName(sqlTypeCodes[0]);

            var properties = Regex.Matches(_sql, @"\{(.*?)\}");

            foreach (Match property in properties)
            {
                var propertyName = property.Groups[1].Value;

                var columnName = criteriaQuery.GetColumn(criteria, propertyName);

                var expression = _ignoreCast ? columnName : string.Format("cast({0} as {1})", columnName, sqlType);

                sql = sql.Replace(property.Value, expression);
            }

            sql = _ignoreCast
                ? string.Format("({0}) as {1}", sql, _columnAliases[0])
                : string.Format("cast({0} as {1}) as {2}", sql, sqlType, _columnAliases[0]);

            return SqlString.Parse(sql);
        }

        public override string ToString()
        {
            return _sql;
        }

        public IType[] GetTypes(ICriteria crit, ICriteriaQuery criteriaQuery)
        {
            return _types;
        }

        public string[] GetColumnAliases(int position, ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            return _columnAliases;
        }

        public string[] GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            return _columnAliases;
        }

        public string[] Aliases
        {
            get { return _aliases; }
        }

        public bool IsGrouped
        {
            get { return _grouped; }
        }

        public bool IsAggregate
        {
            get { return false; }
        }

        public TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery)
        {
            return new TypedValue[0];
        }

        public IType[] GetTypes(string alias, ICriteria crit, ICriteriaQuery criteriaQuery)
        {
            return _types;
        }
    }
}